// Copyright (C) 2006 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// The xml_writer.cpp file contains functions to construct XML documents for
// create/retrieve/update/delete operations on accounts, aliases and mailing
// lists.  Users of the client library should not call these routines directly,
// but should rely instead on the individual methods defined in account.h,
// alias.h and mailing.h.
//
// For more information about the functions defined here, refer to the header
// file xml_writer.h.

#include <libxml/xmlwriter.h>
#include "xml_writer.h"

namespace googleapps {

// XML document encoding to use.
static const char kDocEncoding[] = "UTF-8";

// XML attributes for the root element.
static const char kNamespaceKey[] = "xmlns:hs",
                  kNamespaceValue[] = "google:accounts:rest:protocol",
                  kSchemaKey[] = "xmlns:xsi",
                  kSchemaValue[] = "http://www.w3.org/2001/XMLSchema-instance";

// XML elements used in API requests.
static const char kRestElem[] = "hs:rest",
                  kTypeElem[] = "hs:type",
                  kTokenElem[] = "hs:token",
                  kDomainElem[] = "hs:domain",
                  kQueryKeyElem[] = "hs:queryKey",
                  kQueryDataElem[] = "hs:queryData",
                  kCreateSection[] = "hs:CreateSection",
                  kUpdateSection[] = "hs:UpdateSection";

static const xmlChar* StringToXmlChar(const string &s) {
  return reinterpret_cast<const xmlChar *>(s.c_str());
}

static pair<xmlBufferPtr, xmlTextWriterPtr> GetXmlWriter() {
  // Create a new XML buffer for writing the XML document.
  xmlBufferPtr buf = xmlBufferCreate();
  if (buf == NULL) {
    return make_pair(xmlBufferPtr(NULL), xmlTextWriterPtr(NULL));
  }

  // Create a new XML writer for memory, with no compression.
  xmlTextWriterPtr writer = xmlNewTextWriterMemory(buf, 0);
  if (writer == NULL) {
    return make_pair(buf, xmlTextWriterPtr(NULL));
  }

  // Start the document with the default UTF-8 encoding.
  xmlTextWriterStartDocument(writer, NULL, kDocEncoding, NULL);

  // Start the root element and write its attributes.
  xmlTextWriterStartElement(writer, StringToXmlChar(kRestElem));
  xmlTextWriterWriteAttribute(writer, StringToXmlChar(kNamespaceKey),
                              StringToXmlChar(kNamespaceValue));
  xmlTextWriterWriteAttribute(writer, StringToXmlChar(kSchemaKey),
                              StringToXmlChar(kSchemaValue));

  return make_pair(buf, writer);
}

pair<bool, string> GetCreateRequest(
    const string &type,
    const string &token,
    const string &domain,
    const vector<pair<string, string> > &create_keys) {

  pair<xmlBufferPtr, xmlTextWriterPtr> writer = GetXmlWriter();
  if (writer.first == NULL || writer.second == NULL) {
    return make_pair(false, "Error creating XML writer.");
  }

  // Write the type, token, domain, query key and query data elements.
  xmlTextWriterWriteElement(writer.second, StringToXmlChar(kTypeElem),
                            StringToXmlChar(type));
  xmlTextWriterWriteElement(writer.second, StringToXmlChar(kTokenElem),
                            StringToXmlChar(token));
  xmlTextWriterWriteElement(writer.second, StringToXmlChar(kDomainElem),
                            StringToXmlChar(domain));

  // Write the CreateSection and each key-value pair within it.
  xmlTextWriterStartElement(writer.second, StringToXmlChar(kCreateSection));
  for (unsigned int i = 0; i < create_keys.size(); i++) {
    xmlTextWriterWriteElement(writer.second,
                              StringToXmlChar(create_keys[i].first),
                              StringToXmlChar(create_keys[i].second));
  }

  // End the document.
  xmlTextWriterEndDocument(writer.second);

  // Clean up and return the resulting XML string.
  xmlFreeTextWriter(writer.second);
  string result = reinterpret_cast<const char *>(writer.first->content);
  xmlBufferFree(writer.first);

  return make_pair(true, result);
}

pair<bool, string> GetUpdateRequest(
    const string &type,
    const string &token,
    const string &domain,
    const string &queryKey,
    const string &queryData,
    const vector<pair<string, string> > &update_keys) {

  pair<xmlBufferPtr, xmlTextWriterPtr> writer = GetXmlWriter();
  if (writer.first == NULL || writer.second == NULL) {
    return make_pair(false, "Error creating XML writer.");
  }

  // Write the type, token, domain, query key and query data elements.
  xmlTextWriterWriteElement(writer.second, StringToXmlChar(kTypeElem),
                            StringToXmlChar(type));
  xmlTextWriterWriteElement(writer.second, StringToXmlChar(kTokenElem),
                            StringToXmlChar(token));
  xmlTextWriterWriteElement(writer.second, StringToXmlChar(kDomainElem),
                            StringToXmlChar(domain));
  xmlTextWriterWriteElement(writer.second, StringToXmlChar(kQueryKeyElem),
                            StringToXmlChar(queryKey));
  xmlTextWriterWriteElement(writer.second, StringToXmlChar(kQueryDataElem),
                            StringToXmlChar(queryData));

  // Write the UpdateSection and each key-value pair within it.
  xmlTextWriterStartElement(writer.second, StringToXmlChar(kUpdateSection));
  for (unsigned int i = 0; i < update_keys.size(); i++) {
    xmlTextWriterWriteElement(writer.second,
                              StringToXmlChar(update_keys[i].first),
                              StringToXmlChar(update_keys[i].second));
  }

  // End the document.
  xmlTextWriterEndDocument(writer.second);

  // Clean up and return the resulting XML string.
  xmlFreeTextWriter(writer.second);
  string result = reinterpret_cast<const char *>(writer.first->content);
  xmlBufferFree(writer.first);

  return make_pair(true, result);
}

static pair<bool, string> GetRetrieveDeleteTemplate(
    const string &type,
    const string &token,
    const string &domain,
    const string &queryKey,
    const string &queryData) {

  pair<xmlBufferPtr, xmlTextWriterPtr> writer = GetXmlWriter();
  if (writer.first == NULL || writer.second == NULL) {
    return make_pair(false, "Error creating XML writer.");
  }

  // Write the type, token, domain, query key and query data elements.
  xmlTextWriterWriteElement(writer.second, StringToXmlChar(kTypeElem),
                            StringToXmlChar(type));
  xmlTextWriterWriteElement(writer.second, StringToXmlChar(kTokenElem),
                            StringToXmlChar(token));
  xmlTextWriterWriteElement(writer.second, StringToXmlChar(kDomainElem),
                            StringToXmlChar(domain));
  xmlTextWriterWriteElement(writer.second, StringToXmlChar(kQueryKeyElem),
                            StringToXmlChar(queryKey));
  xmlTextWriterWriteElement(writer.second, StringToXmlChar(kQueryDataElem),
                            StringToXmlChar(queryData));

  // End the document.
  xmlTextWriterEndDocument(writer.second);

  // Clean up and return the resulting XML string.
  xmlFreeTextWriter(writer.second);
  string result = reinterpret_cast<const char *>(writer.first->content);
  xmlBufferFree(writer.first);

  return make_pair(true, result);
}

pair<bool, string> GetRetrieveRequest(const string &type,
                                      const string &token,
                                      const string &domain,
                                      const string &queryKey,
                                      const string &queryData) {
  return GetRetrieveDeleteTemplate(type, token, domain, queryKey, queryData);
}

pair<bool, string> GetDeleteRequest(const string &type,
                                    const string &token,
                                    const string &domain,
                                    const string &queryKey,
                                    const string &queryData) {
  return GetRetrieveDeleteTemplate(type, token, domain, queryKey, queryData);
}

} // End of googleapps namespace
